💭
← Blog
June 3, 2025
Mastering Keycloak Configuration with GitOps and keycloak-config-cli
Automated IAM Configuration with Git and keycloak-config-cli

current image in my mind.

current image in my mind.

In the realm of Identity and Access Management (IAM), Keycloak stands out as a powerful, open-source solution. Yet, managing its configuration—especially in complex, multi-tenant environments—can quickly become a significant bottleneck. Manual changes through the UI, often referred to as “Click-Ops” lead to inconsistent environments, lack of auditability, and high operational overhead. Multi-tenant SaaS applications face a critical challenge: how to efficiently and securely onboard hundreds or thousands of customers, each requiring their own isolated identity management space. Manual “Click-Ops” in Keycloak simply doesn’t scale, as managing multiple realms for tenants degrades performance and increases complexity. Enter the KTA (Keycloak Tenant Accelerator) a comprehensive demonstration of how GitOps principles combined with the keycloak-config-cli tool can transform tenant onboarding. This approach automates what was once a manual bottleneck, turning it into a fully automated, auditable, and scalable Configuration as Code (CaC) process. By leveraging a GitOps workflow, every configuration change is version-controlled and traceable, enabling teams to deploy consistent Keycloak setups across environments with confidence.


1. The Legacy Challenge: “Click-Ops” in keycloak and versioning.

Managing Keycloak manually is a common starting point, but it presents several challenges that grow with your application’s scale.

Keycloak's Admin Console with a tedious, multi-step process of manually creating and configuring realms. 

Keycloak’s Admin Console with a tedious, multi-step process of manually creating and configuring realms. 

These problems highlight the need for a more systematic, automated, and auditable approach to managing Keycloak’s configuration


2. Keycloak-config-cli: your Key to Declarative Keycloak Management.

This is where the open-source adorsys/keycloak-config-cli tool becomes essential. It’s a Java-based command-line utility designed to manage Keycloak’s configuration declaratively.

3. The KTA Project: Automated Tenant Onboarding at Scale

The Keycloak Tenant Accelerator (KTA) project demonstrates exactly how a real SaaS company can automate the complete lifecycle of tenant onboarding. Let’s imagine a project management SaaS called “ConnectFlow” that needs to onboard companies like “x-company” as isolated tenants.

Screenshot 2025-07-01 at 18.24.11.png

4. The Template-Driven Approach: Configuration as Code at Scale

The power of the KTA system lies in its template-driven approach to generating tenant configurations. Instead of manually creating each tenant’s setup, everything starts with a master blueprint. The master template (_templates/tenant-template.yaml) defines the complete structure of a Keycloak realm using placeholder variables.

Screenshot 2025-07-02 at 01.24.49.png

# Tenant Realm Template
realm: "{{TENANT_ID}}"
enabled: true
displayName: "{{TENANT_NAME}} Services"

# Security and Session Configuration
bruteForceProtected: true
accessTokenLifespan: 300  # 5 minutes
ssoSessionIdleTimeout: 1800  # 30 minutes
passwordPolicy: "length(8) and digits(1) and lowerCase(1) and upperCase(1) and specialChars(1)"

# Tenant Applications
clients:
  - clientId: "{{TENANT_ID}}-webapp"
    name: "{{TENANT_NAME}} Web Application"
    enabled: true
    publicClient: true
    redirectUris:
      - "https://{{TENANT_ID}}.kta.app/*"
      - "http://localhost:3000/*"  # For development
    
  - clientId: "{{TENANT_ID}}-api"
    name: "{{TENANT_NAME}} API Client" 
    enabled: true
    publicClient: false
    serviceAccountsEnabled: true
    secret: "{{TENANT_ID}}-api-secret-change-me"

# Role-Based Access Control
roles:
  realm:
    - name: "tenant_admin"
      description: "Administrator role for {{TENANT_NAME}} tenant"
    - name: "tenant_user"
      description: "Standard user role for {{TENANT_NAME}} tenant"

# Multi-tenant Protocol Mappers
protocolMappers:
  - name: "tenant-id-mapper"
    protocol: "openid-connect"
    protocolMapper: "oidc-hardcoded-claim-mapper"
    config:
      "claim.name": "tenant_id"
      "claim.value": "{{TENANT_ID}}"
# Generated tenants/acme_corp.yaml
realm: "acme_corp"
displayName: "ACME Corporation Services"
clients:
  - clientId: "acme_corp-webapp"
    name: "ACME Corporation Web Applic ation"
    redirectUris:
      - "https://acme_corp.kta.app/*"
protocolMappers:
  - config:
      "claim.value": "acme_corp"

This level of isolation ensures that each tenant’s users, applications, and data remain completely separate from other tenants on the same Keycloak server.

Screenshot 2025-07-01 at 18.39.26.png

5. CI/CD Pipeline: From Configuration to Deployment

Our Actions workflow automates the entire process from tenant configuration changes to deployment in Keycloak. This ensures consistency and reliability across all tenant deployments.

The Pipeline in Detail:

flowchart TD
    A[Config Change] --> B[Validate Config]
    A --> C[Check Dependencies]
    C --> D[Apply Configuration]
    B --> D
    D --> E[If Success]
    D --> F[If Failure]
    E --> G[Update Status]
    F --> H[Rollback]

This automation yields powerful results, enabling seamless tenant management:

tool logs demonstrating successful realm creation and updates

tool logs demonstrating successful realm creation and updates

Example CI setup for Production.

name: Apply Keycloak Tenant Configurations

on:
  push:
    branches:
      - main
      - master
    paths:
      - 'keycloak-configs/tenants/**/*.yaml'
      - 'keycloak-configs/tenants/**/*.yml'
  
  # Allow manual triggering
  workflow_dispatch:
    inputs:
      tenant_id:
        description: 'Specific tenant ID to deploy (optional - leave empty for all)'
        required: false
        type: string

jobs:
  validate-configs:
    name: Validate Configuration Files
    runs-on: ubuntu-latest
    outputs:
      configs-valid: ${{ steps.validate.outputs.valid }}
      tenant-files: ${{ steps.find-files.outputs.files }}
    
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4
      
      - name: Set up Python
        uses: actions/setup-python@v4
        with:
          python-version: '3.11'
      
      - name: Install PyYAML
        run: pip install PyYAML
      
  deploy-to-keycloak:
    name: Deploy Configurations to Keycloak
    runs-on: ubuntu-latest
    needs: validate-configs
    if: needs.validate-configs.outputs.configs-valid == 'true'
    
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4
      
      - name: Create temporary config directory
        run: |
          mkdir -p /tmp/keycloak-configs
          files="${{ needs.validate-configs.outputs.tenant-files }}"
          echo " Files to process: $files"
          
          for file in $files; do
            if [ -f "$file" ]; then
              filename=$(basename "$file")
              cp "$file" "/tmp/keycloak-configs/$filename"
              echo "Copied $file → /tmp/keycloak-configs/$filename"
            else
              echo "File not found: $file"
            fi
          done
          
          echo "Contents of /tmp/keycloak-configs/:"
          ls -la /tmp/keycloak-configs/
          echo "File count: $(ls -1 /tmp/keycloak-configs/*.yaml /tmp/keycloak-configs/*.yml 2>/dev/null | wc -l)"
      
      - name: Validate Keycloak Connection Settings
        run: |
          echo "Validating Keycloak connection settings..."
          if [ -z "${{ secrets.KEYCLOAK_URL }}" ]; then
            echo " Error: KEYCLOAK_URL secret not set"
            echo "Please set the following repository secrets:"
            echo "  - KEYCLOAK_URL (e.g., https://your-keycloak.com or https://xxxx.ngrok.io)"
            echo "  - KEYCLOAK_ADMIN_USER (e.g., admin)"
            echo "  - KEYCLOAK_ADMIN_PASSWORD (e.g., your-password)"
            exit 1
          fi
          echo " Keycloak URL: ${{ secrets.KEYCLOAK_URL }}"
          echo "Admin User: ${{ secrets.KEYCLOAK_ADMIN_USER }}"
          echo "Credentials configured"

      - name: Apply Keycloak Configurations
        run: |
          echo "Starting Keycloak configuration deployment..."
          echo "Available files in /config:"
          ls -la /tmp/keycloak-configs/
          
          docker run --rm \
            -v /tmp/keycloak-configs:/config \
            -e KEYCLOAK_URL="${{ secrets.KEYCLOAK_URL }}" \
            -e KEYCLOAK_USER="${{ secrets.KEYCLOAK_ADMIN_USER }}" \
            -e KEYCLOAK_PASSWORD="${{ secrets.KEYCLOAK_ADMIN_PASSWORD }}" \
            -e KEYCLOAK_AVAILABILITYCHECK_ENABLED=true \
            -e KEYCLOAK_AVAILABILITYCHECK_TIMEOUT=120s \
            -e IMPORT_FILES_LOCATIONS='/config/*.yaml' \
            -e IMPORT_VAR_SUBSTITUTION_ENABLED=false \
            -e IMPORT_CACHE_ENABLED=false \
            -e LOGGING_LEVEL_KEYCLOAKCONFIGCLI=INFO \
            adorsys/keycloak-config-cli:latest

This setup provides a robust foundation for managing multiple tenants through version control. The pipeline ensures that configuration changes are properly validated and applied, with automatic rollback in case of failures.


6. Evolution: Keycloak Organizations - The Future of Multi-Tenancy

The Organization Revolution:

While the realm-per-tenant approach demonstrated above provides excellent isolation, Keycloak 26+ introduces a game-changing feature: Organizations This native multi-tenancy solution offers a more scalable and efficient approach to managing multiple tenants within a single realm.

Realm-per-Tenant vs Organizations: A Comprehensive Comparison:

AspectRealm-per-TenantOrganizations
Scalabilityperformance issues becoming noticeable around 400 and potentially rendering system unstableVirtually unlimited organizations
Resource UsageHigh (each realm has overhead)Low (shared realm infrastructure)
Cross-tenant FeaturesComplex to implementBuilt-in identity-first login
AdministrationMultiple realm managementSingle realm with organization delegation
SSO ScenariosLimited cross-realm SSONative cross-organization SSO
Domain-based RoutingManual implementationBuilt-in domain-to-organization mapping

The KTA system manage Keycloak Organizations using a fully declarative, GitOps-driven workflow. This approach abandons direct API calls for organization creation in favor of the same powerful keycloak-config-cli methodology used for realm-per-tenant management. The result is a single, consistent, and auditable process for all multi-tenancy models..

This new architecture is simpler and more robust. The backend’s only role is to render a complete configuration file from a template and commit it to Git. The CI/CD pipeline handles the deployment declaratively.

kta organization: Declarative GitOps Flow

kta organization: Declarative GitOps Flow

The core of this new approach is a set of self-contained configuration files to reprensent your desire state, this is purely owesome.

1. The Organizations Realm Template (_templates/organizations-realm-template.yaml)

First, a base realm is defined to host all organizations. This is a one-time setup.

# KTA Organizations Realm Template
# This template creates a single realm that will host multiple organizations.
# Organizations are defined in separate files and merged at apply-time.

realm: "kta-organizations"
enabled: true
displayName: "KTA Multi-Tenant Organizations"

# Enable Organizations feature
attributes:
  "organizationsEnabled": "true"

# Other realm settings...
registrationAllowed: false
registrationEmailAsUsername: true
rememberMe: true
verifyEmail: true
loginWithEmailAllowed: true
duplicateEmailsAllowed: false
resetPasswordAllowed: true
editUsernameAllowed: false
bruteForceProtected: true
accessTokenLifespan: 300
ssoSessionIdleTimeout: 1800
ssoSessionMaxLifespan: 36000
internationalizationEnabled: true
supportedLocales:
  - "en"
  - "fr"
  - "es"
  - "de"
defaultLocale: "en"
passwordPolicy: "length(8) and digits(1) and lowerCase(1) and upperCase(1) and specialChars(1)"
eventsEnabled: true
eventsListeners:
  - "jboss-logging"
adminEventsEnabled: true
adminEventsDetailsEnabled: true

# Realm-level roles for organization management
roles:
  realm:
    - name: "org_admin"
      description: "Organization administrator role"
    - name: "org_manager"
      description: "Organization manager role"
    - name: "org_user"
      description: "Organization user role"
    - name: "org_viewer"
      description: "Organization viewer role"
    - name: "kta_super_admin"
      description: "KTA super administrator - can manage all organizations"

# Placeholder for organizations.
# This list will be populated by merging individual organization config files.
organizations: [] 

2. The Jinja2 Organization Template (_templates/organization-template.yaml.j2)

This is the master blueprint for a single organization. It’s a .j2 file, signifying it as a Jinja2 template, which prevents conflicts with YAML linters. The backend renders this template to produce a complete YAML file.

# KTA Organization Template
# This template creates a self-contained configuration file for a single organization
# that can be directly applied by keycloak-config-cli.

realm: "kta-organizations"
organizations:
  - name: "{{ org_name }}"
    alias: "{{ org_alias }}"
    enabled: true
    description: "Organization for {{ org_name }}"
    attributes:
      industry:
        - "{{ industry | default('Technology') }}"
      region:
        - "{{ region | default('Global') }}"
    domains:
      {%- for domain in domains %}
      - name: "{{ domain.name }}"
        verified: {{ domain.verified | default(false) | tojson }}
      {%- endfor %}
    members:
      - username: "{{ admin_email }}"
        firstName: "{{ admin_first_name }}"
        lastName: "{{ admin_last_name }}"
        email: "{{ admin_email }}"
        enabled: true
        realmRoles:
          - "org_admin" 

3. The Generated configuration (organizations/acme-corp.yaml)

When a user signs up, the backend generates a complete, self-contained YAML file like this. Note that it specifies the target realm (kta-organizations) and contains all necessary organization details.

realm: "kta-organizations"
organizations:
  - name: "Acme Corporation"
    alias: "acme-corp"
    enabled: true
    description: "Organization for Acme Corporation"
    attributes:
      industry:
        - "Manufacturing"
      region:
        - "North America"
    domains:
      - name: "acme-corp.com"
      - name: "acme.com"
    members:
      - username: "admin@acme-corp.com"
        firstName: "John"
        lastName: "Doe"
        email: "admin@acme-corp.com"
        enabled: true 

Screenshot 2025-07-02 at 12.42.46.png

Screenshot 2025-07-02 at 12.43.02.png

3. CI/CD Pipeline (.github/workflows/apply-organizations-config.ym**l)**

The GitHub Actions workflow is now extremely streamlined. It triggers on a push to the organizations directory and executes a single script.

apply_all_org_configs() {
    print_info "Applying all organization configurations..."
    
    if [ ! -d "$ORGS_DIR" ] || [ -z "$(ls -A "$ORGS_DIR")" ]; then
        print_info "No organization files found in $ORGS_DIR. Nothing to apply."
        return
    fi

    for file in "$ORGS_DIR"/*.yaml; do
        if [ -f "$file" ]; then
            filename=$(basename "$file")
            print_info "Applying organization config: $filename"
            
            docker run --rm \
                --network "$NETWORK_NAME" \
                -v "$ORGS_DIR:/config/organizations" \
                -e KEYCLOAK_URL="$KEYCLOAK_URL" \
                -e KEYCLOAK_USER="$KEYCLOAK_USER" \
                -e KEYCLOAK_PASSWORD="$KEYCLOAK_PASSWORD" \
                -e IMPORT_FILES_LOCATIONS="/config/organizations/$filename" \
                -e IMPORT_VAR_SUBSTITUTION_ENABLED=false \
                -e LOGGING_LEVEL_DE_ADORSYS_KEYCLOAK_CONFIG_CLI=INFO \
                "$KEYCLOAK_CONFIG_CLI_IMAGE"
        fi
    done

    print_status "All organization configurations applied."
}

Screenshot 2025-07-02 at 12.48.04.png

4. Summary of new approach

  1. When to Use Organization vs Realm-per-Tenant

    Choose Organizations whenChoose Realm-per-Tenant when
    Managing 50+ tenantsNeed maximum isolation
    Need cross-tenant user sharingHave complex per-tenant customizations
    Require domain-based routingRequire different authentication flows per tenant
    Want simplified administrationRegulatory compliance demands complete separation
    Plan for massive scale (1000+ tenants)Managing <50 tenants with stable requirements

7. Migration Strategy: KTA methodology + Organizations

The KTA project demonstrates how organizations can gradually migrate from realm-per-tenant to Organizations:

from Realms to Organizations.

from Realms to Organizations.


8. Demonstration Resources

To implement this solution in your environment, refer to these key resources:

Setup and Installation

**Configuration Examples


9. Future Enhancements, Performance and Scalability Considerations

Multi-Environment Support, advance valiation, monitoring and reporting


10. Best Practices and Practical Tips

   # tenant-security.yaml
   realm: "{{TENANT_ID}}"
   bruteForceProtected: true
   failureFactor: 3
   waitIncrementSeconds: 60
   maxFailureWaitSeconds: 900
   maxDeltaTimeSeconds: 43200
   passwordPolicy: "length(12) and digits(2) and upperCase(1) and lowerCase(1) and specialChars(1)"
   
   # Client configuration with secure defaults
   clients:
     - clientId: "{{TENANT_ID}}-api"
       secret: "${ENV_CLIENT_SECRET}"  # Loaded from environment
       publicClient: false
       directAccessGrantsEnabled: false
       serviceAccountsEnabled: true
       standardFlowEnabled: false
       implicitFlowEnabled: false
   # prometheus-rules.yaml
   groups:
     - name: keycloak_alerts
       rules:
         - alert: KeycloakHighFailedLogins
           expr: rate(keycloak_failed_login_attempts[5m]) > 10
           labels:
             severity: warning
         - alert: KeycloakConfigUpdateFailed
           expr: keycloak_config_cli_status{status="failed"} > 0
           labels:
             severity: critical

11. Additional Resources and Further Reading

For those interested in exploring further, here are some valuable resources:

10. Conclusion

The implementation of automated Keycloak tenant configuration using GitHub Actions represents a significant advancement in managing multi-tenant environments. This solution not only streamlines the deployment process but also ensures consistency, security, and scalability. By following the best practices and considering future enhancements, organizations can build a robust and efficient tenant management system.

Thanks for sticking with me till the end, and I hope this deep dive inspires you to explore new ways to modernize your own projects! Feel free to contribute to the project, share your experiences, or reach out with questions. Together, we can continue to improve and evolve the way we manage identity and access in our applications.